package com.bah.tract.security;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.UnsupportedCallbackException;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.sun.xml.wss.impl.callback.KeyStoreCallback;

/**
 * A CallbackHandler that can expose a KeyStore that is read from a configurable
 * location and that is protected via an encrypted password.
 */
public class TrustStoreCallbackImpl implements
		javax.security.auth.callback.CallbackHandler {

	/**
	 * Logger for the class.
	 */
	private static final Log LOG = LogFactory
			.getLog(TrustStoreCallbackImpl.class);

	/**
	 * Keystore that the callback handler exposes.
	 */
	private transient KeyStore keyStore;

	/**
	 * Default constructor. Used to create the default KeyStore.
	 */
	public TrustStoreCallbackImpl() {
		this("c:/projects/tract/trustStore.jks", "changeit");
	}

	/**
	 * Utility constructor. Used to expose a different KeyStore.
	 * 
	 * @param locProperty
	 *            Configuration property that contains the path to the KeyStore.
	 * @param passProperty
	 *            Configuration property that contains the password to the
	 *            KeyStore. This can be encrypted.
	 */
	public TrustStoreCallbackImpl(final String locProperty,
			final String passProperty) {
		try {
			keyStore = KeyStore.getInstance("JKS");
		} catch (final java.security.KeyStoreException e) {
			throw new KeyStoreException(e);
		}
		final String trustStoreLoc = locProperty;
		final String password = passProperty;
		FileInputStream fis = null;
		try {
			fis = FileUtils.openInputStream(new File(trustStoreLoc));
			keyStore.load(fis, password.toCharArray());
		} catch (final IOException e) {
			throw new KeyStoreException(e);
		} catch (final NoSuchAlgorithmException e) {
			throw new KeyStoreException(e);
		} catch (final CertificateException e) {
			throw new KeyStoreException(e);
		} finally {
			IOUtils.closeQuietly(fis);
		}
		LOG.info("Successfully loaded trustStore/keyStore from "
				+ trustStoreLoc);
	}

	/**
	 * Gets the keyStore.
	 * 
	 * @return the keyStore.
	 */
	protected final KeyStore getKeyStore() {
		return keyStore;
	}

	/**
	 * A hook that allows subclasses to support additional callbacks while this
	 * class supports KeyStoreCallback.
	 * 
	 * @param callback
	 *            The requested callback.
	 * @throws IOException
	 *             if an error occurs responding to the callback.
	 * @throws UnsupportedCallbackException
	 *             if the callback is not supported.
	 */
	protected void handle(final Callback callback) throws IOException,
			UnsupportedCallbackException {
		throw new UnsupportedCallbackException(callback);
	}

	/**
	 * @see javax.security.auth.callback.CallbackHandler#handle(javax.security.auth.callback.Callback[]
	 *      callbacks)
	 */
	public final void handle(final Callback[] callbacks) throws IOException,
			UnsupportedCallbackException {
		for (final Callback callback : callbacks) {
			if (callback instanceof KeyStoreCallback) {
				final KeyStoreCallback keyStoreCallback = (KeyStoreCallback) callback;
				LOG.info("Setting trustStore/keyStore in callback...");
				keyStoreCallback.setKeystore(keyStore);
			} else {
				handle(callback);
			}
		}
	}

}
